import type { ServerConnect } from '../connect'
import { ServerConnectAction } from '../action'
import type { VerifyInfo } from '@/connect/types'
import { sleep } from '@/utils/common'

/**
 * 接收验证
 */
export class ReceiveVerifyAction extends ServerConnectAction {
  constructor(connect: ServerConnect) {
    super('receiveVerify', connect)

    this.addAsyncAction('receiveVerifyInfo', this.receiveVerifyInfo_handler, true)
  }

  timeout = 3 * 60 * 1000
  isTimeout = false
  timer?: NodeJS.Timeout

  async receiveVerifyInfo_handler() {
    this.isTimeout = false
    this.timer = setTimeout(() => {
      this.isTimeout = true
      this.verifyFail('timeout').catch((err) => err)
    }, this.timeout)

    const p = await this.connect.packetOnceAsync()

    if (this.isTimeout) {
      throw new Error('timeout')
    } else {
      clearTimeout(this.timer)
      this.timer = void 0
    }

    const info = p.readBodyAsJson()
    if (!info) await this.verifyFail('verify info is empty')

    if (!info.command) await this.verifyFail('command is empty')

    this.logger.info('verify info', Object.assign({}, info, { password: '******' }))

    switch (info.command) {
      case 'send':
        await this.verifyCommand_send(info)
        break
      default:
        await this.verifyFail('unknown command')
        break
    }

    this.logger.info('verify success')
    this.nextName = info.command
    this.connect.verified = true
    // 验证成功移除校验失败记录
    this.connect.limiter.removeVerifyFailureRecord(this.connect.remoteAddressInfo.address)
    await this.connect.sendOk()
  }

  async verifyCommand_send(info: VerifyInfo) {
    if (!info.name || !info.password) await this.verifyFail('name or password is empty')

    const project = this.connect.serverInstance.getProjectByName(info.name)

    if (!project) return await this.verifyFail('name or password error')

    if (project.connecting) return await this.verifyFail('The Project is in the connection', false)

    this.connect.project = project
    project.setConnect(this.connect)

    if (!project.verifyPassword(info.password)) await this.verifyFail('name or password error')
  }

  async verifyFail(err = 'verify fail', addRecord = true) {
    this.connect.verified = false
    if (addRecord) {
      this.connect.limiter.addVerifyFailureRecord(this.connect.remoteAddressInfo.address)
    }
    this.logger.error(err)
    this.connect.sendError(err)
    await sleep(300)
    throw new Error(err)
  }
}
